<?php

/**
 * # 常用函数
 */


if (!function_exists('message')) {
    /**
     * 组装信息
     *
     * @param string $message
     * @param array  $context
     *
     * @return string
     */
    function message(string $message, array $context = []): string
    {
        $replace = [];
        if (!empty($context) and is_array($context)) {
            foreach ($context as $key => $val) {
                $replace['${' . $key . '}'] = is_array($val) ? '[...]' : (is_object($val) ? '{...}' : $val);
            }
        }
        return strtr($message, $replace);
    }
}
//if (!function_exists('html')) {
//    /**
//     * 模板输出
//     * @param $var
//     * @param $template
//     * @return string
//     */
//    function html($var, $template): string
//    {
//        extract($var);
//        unset($var);
//        ob_start();
//        include $template;
//        return ob_get_clean();
//    }
//}
if (!function_exists('dump')) {
    /**
     * 浏览器友好的变量输出
     *
     * @param mixed    $var   变量
     * @param boolean  $echo  是否输出 默认为true 如果为false 则返回输出字符串
     * @param ?string  $label 标签 默认为空
     * @param ?integer $flags html-special-chars-flags
     *
     * @return ?string
     */
    function dump(mixed $var, bool $echo = true, ?string $label = null, ?int $flags = ENT_COMPAT | ENT_HTML401): ?string
    {
        $label = (null === $label) ? '' : rtrim($label) . ':';
        ob_start();
        var_dump($var);
        $output = ob_get_clean();
        $output = preg_replace('/]=>\n(\s+)/m', '] => ', $output);
        
        if (!extension_loaded('xdebug')) {
            $output = htmlspecialchars($output, $flags);
        }
        $output = '<pre>' . $label . $output . '</pre>';
        
        if ($echo) {
            echo($output);
            return null;
        } else {
            return $output;
        }
    }
}
//if (!function_exists('format_memory_size')) {
//    /**
//     * 存储容量格式化
//     *
//     * @param int|float $size 容量
//     * @return string
//     */
//    #[Pure]
//    function format_memory_size(float|int $size): string
//    {
//        $a = ['B', 'KB', 'MB', 'GB', 'TB'];
//        $pos = 0;
//        while ($size >= 1024) {
//            $size /= 1024;
//            $pos++;
//        }
//        return round($size) . " " . $a[$pos];
//    }
//}
//if (!function_exists('php2html')) {
//    function php2html($var): string {
//        return highlight_string(var_export($var, true), true);
//    }
//}

//  类相关函数
if (!function_exists('is_classname')) {
    function is_classname($class): bool | int
    {
        /**
         * 反斜线在单引号字符串和双引号字符串中都有特殊含义，
         * 因此要匹配一个反斜线， 模式中必须写为 ”\\\\”。
         * 如: '/\\/'
         *     首先它作为字符串，反斜线会进行转义， 那么转义后的结果是/\/，
         *     而正则表达式引擎也认为 \ 是转义标记，它会将分隔符 / 进行转义， 从而得到的是一个错误。
         *  因此，需要 4 个反斜线才可以匹配一个反斜线。
         */
        return preg_match('%^[\\\\a-z][\\\\_a-z0-9]*$%i', $class);
    }
}
if (!function_exists('class_basename')) {
    /**
     * 返回不包含命名空间的类名
     *
     * @param string|object $class
     *
     * @return string
     */
    function class_basename(object | string $class): string
    {
        $class = is_object($class) ? get_class($class) : $class;
        return basename(str_replace('\\', '/', $class));
    }
}
if (!function_exists('class_namespace')) {
    /**
     * 返回命名空间
     *
     * @param string|object $class
     *
     * @return string
     */
    function class_namespace(object | string $class): string
    {
        $class = is_object($class) ? get_class($class) : $class;
        
        $pos = strrpos($class, '\\');
        
        return ($pos !== false) ? substr($class, 0, $pos) : '';
    }
}
if (!function_exists('separate_classname')) {
    /**
     * 拆分类名
     *
     * @param string  $class
     * @param ?string $classname
     * @param ?string $namespace
     *
     * @return bool
     */
    function separate_classname(string $class, ?string &$classname, ?string &$namespace): bool
    {
        if (!is_classname($class)) return false;
        
        $classname = class_basename($class);
        $namespace = class_namespace($class);
        
        return true;
    }
}

//  数组函数
if (!function_exists('array_get')) {
    /**
     * Get an item from an array using "dot" notation.
     *
     * @param array      $array
     * @param string     $key
     * @param mixed|null $default
     *
     * @return mixed
     */
    function array_get(array $array, string $key, mixed $default = null): mixed
    {
        array_has($array, $key, $default);
        return $default;
    }
}
if (!function_exists('array_has')) {
    /**
     * Check if an item exists in an array using "dot" notation.
     *
     * @param array      $array
     * @param string     $key
     * @param mixed|null $value
     *
     * @return bool
     */
    function array_has(array $array, string $key, mixed &$value = null): bool
    {
        if (empty($array)) return false;
        
        foreach (explode('.', $key) as $segment) {
            if (!is_array($array) || !array_key_exists($segment, $array)) {
                return false;
            }
            $array = $array[$segment];
        }
        $value = $array;
        return true;
    }
}
if (!function_exists('array_set')) {
    /**
     * Set an array item to a given value using "dot" notation.
     *
     * If no key is given to the method, the entire array will be replaced.
     *
     * @param array  $array
     * @param string $key
     * @param        $value
     *
     * @return bool  isModified
     */
    function array_set(array &$array, string $key, $value): bool
    {
        
        $keys = explode('.', $key);
        
        $modified = false;
        while (count($keys) > 0) {
            $key = array_shift($keys);
            // If the key doesn't exist at this depth, we will just create an empty array
            // to hold the next value, allowing us to create the arrays to hold final
            // values at the correct depth. Then we'll keep digging into the array.
            if (!isset($array[$key]) || !is_array($array[$key])) {
                $array[$key] = [];
                $modified    = true;
            }
            
            $array =& $array[$key];
        }
        
        if ($array === $value) return $modified;
        
        $array = $value;
        
        return true;
    }
}
if (!function_exists('array_forget')) {
    /**
     * Remove one or many array items from a given array using "dot" notation.
     *
     * @param array        $array
     * @param array|string $keys
     *
     * @return bool isModified
     */
    function array_forget(array &$array, array | string $keys): bool
    {
        $original =& $array;
        
        $modified = false;
        
        foreach ((array)$keys as $key) {
            $parts = explode('.', $key);
            while (count($parts) > 1) {
                $part = array_shift($parts);
                if (isset($array[$part]) && is_array($array[$part])) {
                    $array =& $array[$part];
                }
            }
            unset($array[array_shift($parts)]);
            $modified = true;
            // clean up after each pass
            $array =& $original;
        }
        
        return $modified;
    }
}
if (!function_exists('array_complete')) {
    /**
     * 完整化数组 [a=>A,b] => [a=>A,b=>default]
     *
     * @param            $array
     * @param mixed|null $default
     *
     * @return array
     */
    function array_complete($array, mixed $default = null): array
    {
        if (!is_array($array)) return [$array => $default];
        $result = [];
        foreach ($array as $key => $value) {
            if (is_int($key) and is_string($value)) {
                $result[$value] = $default;
            } else {
                $result[$key] = $value;
            }
        }
        return $result;
    }
}
if (!function_exists('array_column_unique')) {
    /**
     * Returns an array of unique values representing a single column from the input
     * array.
     *
     * @param array $array     A multidimensional array from which to pull a
     *                         column of values.
     * @param mixed $columnKey The column of values to return.
     *
     * @return array
     */
    function array_column_unique(array $array, mixed $columnKey): array
    {
        
        $result = [];
        foreach ($array as $subArray) {
            if (!is_array($subArray) or !array_key_exists($columnKey, $subArray)) {
                continue;
            } elseif (is_scalar($subArray[$columnKey])) {
                $result[$subArray[$columnKey]] = true;
            }
        }
        return array_keys($result);
    }
}
if (!function_exists('array_isset')) {
    /**
     * isset语句函数 isset($arr[$key])?$arr[$key]:default
     *
     * @param $arr
     * @param $key
     * @param $default
     *
     * @return mixed
     */
    function array_isset($arr, $key, $default = null): mixed
    {
        return ((is_array($arr) and isset($arr[$key])) ? $arr[$key] : $default);
    }
}
if (!function_exists('array_remove')) {
    function array_remove(array &$array, $key, $default = null)
    {
        if (array_has($array, $key, $value)) {
            array_forget($array, $key);
            return $value;
        }
        return $default;
    }
}
if (!function_exists('array_not_set')) {
    /**
     * if (!isset($arr[$key])) $arr[$key] = default
     *
     * @param array $arr
     * @param string|int $key
     * @param null $default
     *
     * @return mixed
     */
    function array_not_set(array $arr, string | int $key, $default = null): array
    {
        if(!isset($arr[$key])) $arr[$key] = $default;
        return $arr;
    }
}
//if (!function_exists('array_is_list')) {
//    /**
//     * 数组键为 [0, 1, ..., n]
//     *
//     * @param array $array
//     *
//     * @return bool
//     */
//    function array_is_list(array $array): bool
//    {
//        return array_keys($array) === range(0, count($array) - 1);
//    }
//}
if (!function_exists('array2bit')) {
    /**
     * @param array<int|string> $arr
     *
     * @return int
     */
    function array2bit(array $arr): int
    {
        if (empty($arr)) return 0;
        
        $bit = 0;
        foreach ($arr as $a) {
            $a = intval($a);
            if ($a > 64 || $a < 1) continue;
            $bit |= 1 << ($a - 1);
        }
        return $bit;
    }
}
if (!function_exists('bit2array')) {
    /**
     * @param int $bit
     *
     * @return int[]
     */
    function bit2array(int $bit): array
    {
        $r     = [];
        $index = 0;
        while ($bit) {
            $index++;
            if ($bit & 1) $r[] = $index;
            $bit = $bit >> 1;
        }
        return $r;
    }
}
if (!function_exists('array_assign')) {
    /**
     * 数组适应性赋值操作
     *
     * @param array         $arr
     * @param ?array|string $name
     * @param               $value
     *
     * @return array
     */
    function array_assign(array $arr, null | array | string $name = null, $value = null): array
    {
        if ($name === null) return $arr;
        
        if (is_array($name)) return array_merge_recursive($arr, $name);
        
        if (is_scalar($name)) $arr[$name] = $value;
        
        return $arr;
    }
}
if (!function_exists('array_every')) {
    function array_every($array, $callback): bool
    {
        
        foreach ($array as $key => $value) {
            if ($callback($value, $key)) continue;
            return false;
        }
        return true;
    }
}
if (!function_exists('array_pass')) {
    /**
     * 按照$config的设定, 提取$form中的数据
     *
     * @param array $config  设置
     * @param array $from    来源
     * @param array $to      去向
     * @param bool  $toFirst 原默认值优先
     *
     * @return array
     */
    function array_pass(array $config, array $from, array &$to = [], bool $toFirst = true): array
    {
        array_walk(
            $config,
            function ($value, $key) use ($from, &$to, $toFirst) {
                if (is_array($value)) {
                    $index   = $value[0];
                    $default = $value[1] ?? null;
                } else {
                    $index   = $value;
                    $default = null;
                }
                if (empty($index)) $index = $key;

                if ($toFirst) $default = $to[$key] ?? $default;
                $to[$key] = $from[$index] ?? $default;
            },
        );
        return $to;
    }
}
if (!function_exists('array_revolve')) {
    /**
     * 将数组90°旋转
     *
     * @param $array
     *
     * @return array
     */
    function array_revolve($array): array
    {
        if (!is_array($array)) return [];
        $r = [];
        foreach ($array as $key => $arr) {
            if (!is_array($arr)) continue;
            foreach ($arr as $index => $v) {
                if (!isset($r[$index])) $r[$index] = [];
                $r[$index][$key] = $v;
            }
        }
        
        return $r;
    }
}

//  字符串计算函数
if (!function_exists('str_random')) {
    /**
     * Generate a more truly "random" alpha-numeric string.
     *
     * @param int  $length
     * @param bool $useNum
     *
     * @return string
     */
    function str_random(int $length = 16, bool $useNum = true): string
    {
        if (function_exists('openssl_random_pseudo_bytes')) {
            $bytes = openssl_random_pseudo_bytes($length * 2);
            /** @noinspection PhpStrictComparisonWithOperandsOfDifferentTypesInspection */
            if ($bytes !== false) {
                $filter = ($useNum) ? ['/', '+', '='] :
                    ['/', '+', '=', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
                return substr(str_replace($filter, '', base64_encode($bytes)), 0, $length);
            }
        }
        
        /** @noinspection SpellCheckingInspection */
        $pool = ($useNum) ? '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' :
            'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        
        return substr(str_shuffle(str_repeat($pool, $length)), 0, $length);
    }
}
if (!function_exists('str_is')) {
    /**
     * Determine if a given string matches a given pattern.
     *
     * @param string $pattern
     * @param string $value
     *
     * @return bool
     */
    function str_is(string $pattern, string $value): bool
    {
        if ($pattern == $value) return true;
        
        $pattern = preg_quote($pattern, '#');
        
        // Asterisks are translated into zero-or-more regular expression wildcards
        // to make it convenient to check if the strings starts with the given
        // pattern such as "library/*", making any string check convenient.
        $pattern = str_replace('\*', '.*', $pattern) . '\z';
        
        return (bool)preg_match('#^' . $pattern . '#', $value);
    }
}
if (!function_exists('e')) {
    /**
     * Escape HTML entities in a string.
     *
     * @param string $value
     *
     * @return string
     */
    function e(string $value): string
    {
        return htmlentities($value, ENT_QUOTES, 'UTF-8', false);
    }
}
if (!function_exists('xorTokens')) {
    /**
     * Returns the XOR result of two strings.
     * If the two strings are of different lengths, the shorter one will be padded to the length of the longer one.
     *
     * @param string $token1
     * @param string $token2
     *
     * @return int|string the XOR result
     */
    function xorTokens(string $token1, string $token2): int | string
    {
        $n1 = mb_strlen($token1, '8bit');
        $n2 = mb_strlen($token2, '8bit');
        if ($n1 > $n2) {
            $token2 = str_pad($token2, $n1, $token2);
        } elseif ($n1 < $n2) {
            $token1 = str_pad($token1, $n2, $n1 === 0 ? ' ' : $token1);
        }
        
        return $token1 ^ $token2;
    }
}
if (!function_exists('byteSubStr')) {
    function byteSubStr($string, $start, $length = null): string
    {
        return mb_substr($string, $start, $length === null ? mb_strlen($string, '8bit') : $length, '8bit');
    }
}

// 网络
if (!function_exists('vars_form_path')) {
    /**
     * 将PATH(e/f/p/q)解析到数组里(e=>f,p=>q)
     *
     * @param string $value PATH
     */
    function vars_form_path(string $value): array
    {
        $pairs = [];
        preg_replace_callback(
            '/([A-Za-z]+)\/([A-Za-z0-9]+)/',
            function ($match) use (&$pairs) {
                $pairs[strtolower($match[1])] = strip_tags($match[2]);
            },
            $value,
        );
        return $pairs;
    }
}

if (!function_exists('parse_accept_header')) {
    /**
     * 解析信息头
     *
     * 本函数自动排序
     *
     * ```php
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
     * $accepts = $request->parseAcceptHeader($header);
     * print_r($accepts);
     * // displays:
     * // [
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
     * //           'text/plain' => ['q' => 0.5],
     * // ]
     * ```
     *
     * @param string $header
     *
     * @return array
     */
    function parse_accept_header(string $header): array
    {
        $accepts = [];
        foreach (explode(',', $header) as $i => $part) {
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
            if (empty($params)) continue;
            
            $values = [
                'q' => [$i, array_shift($params), 1],
            ];
            foreach ($params as $param) {
                if (str_contains($param, '=')) {
                    [$key, $value] = explode('=', $param, 2);
                    if ($key === 'q') {
                        $values['q'][2] = (double)$value;
                    } else {
                        $values[$key] = $value;
                    }
                } else {
                    $values[] = $param;
                }
            }
            $accepts[] = $values;
        }
        
        usort(
            $accepts,
            function ($a, $b) {
                $a = $a['q']; // Index, name, q
                $b = $b['q'];
                if ($a[2] > $b[2]) {
                    return -1;
                } elseif ($a[2] < $b[2]) {
                    return 1;
                } elseif ($a[1] === $b[1]) {
                    return $a[0] > $b[0] ? 1 : -1;
                } elseif ($a[1] === '*/*') {
                    return 1;
                } elseif ($b[1] === '*/*') {
                    return -1;
                } else {
                    $wa = $a[1][strlen($a[1]) - 1] === '*';
                    $wb = $b[1][strlen($b[1]) - 1] === '*';
                    if ($wa xor $wb) {
                        return $wa ? 1 : -1;
                    } else {
                        return $a[0] > $b[0] ? 1 : -1;
                    }
                }
            },
        );
        
        $result = [];
        foreach ($accepts as $accept) {
            $name          = $accept['q'][1];
            $accept['q']   = $accept['q'][2];
            $result[$name] = $accept;
        }
        
        return $result;
    }
}

// 验证转化
if (!function_exists('convert_float')) {
    /**
     * @param $var
     *
     * @return boolean
     */
    function convert_float(&$var): bool
    {
        if (!is_numeric($var)) return false;
        $var = floatval($var);
        return true;
    }
}
if (!function_exists('convert_double')) {
    /**
     * @param $var
     *
     * @return boolean
     */
    function convert_double(&$var): bool
    {
        if (!is_numeric($var)) return false;
        $var = doubleval($var);
        return true;
    }
}
if (!function_exists('convert_int')) {
    /**
     * @param $var
     *
     * @return bool
     */
    function convert_int(&$var): bool
    {
        if (!is_numeric($var)) return false;
        $var = intval($var);
        return true;
    }
}
if (!function_exists('convert_bool')) {
    /**
     * $value === true ：<>0 , 'true', '1', stdClass, !empty, 'yes', 'on'
     *
     * @param $var
     *
     * @return bool
     */
    function convert_bool(&$var): bool
    {
        if (is_string($var)) {
            $var = strtolower($var);
            $var = ($var && $var !== 'false' && $var !== 'no');
            return true;
        }
        $var = !empty($var);
        return true;
    }
}
if (!function_exists('convert_ids')) {
    function convert_ids(&$ids): bool
    {
        if (is_array($ids) && array_is_list($ids)) {

            if (array_any($ids, fn($v) => !convert_id($v))) return false;
            
            $ids = array_unique($ids);
            
            // ！！！ 这里有脱壳过程
            if (count($ids) === 1) $ids = $ids[0];
            
            return true;
        }
        
        return convert_id($ids);
    }
}
if (!function_exists('convert_id')) {
    function convert_id(&$id): bool
    {
        return convert_int($id) && $id > 0;
    }
}

if (!function_exists('valid_name')) {
    /**
     * 含有中文名称的name字段校验
     *
     * @param string $var
     *
     * @return bool|int
     */
    function valid_name(string $var): bool | int
    {
        if (empty($var)) return false;
        
        $match = '/^[_\w\x{4e00}-\x{9fa5}]*$/iu';
        return preg_match($match, $var);
    }
}
if (!function_exists('filter_char')) {
    /**
     * 提取字符和数字
     *
     * @param string $str
     *
     * @return string
     */
    function filter_char(string $str): string
    {
        preg_match_all('/[a-zA-Z0-9]/u', $str, $result);
        return implode('', $result[0]);
    }
}

if (!function_exists('del_dir')) {
    /**
     * 删除目录
     *
     * @param string $path
     *
     * @return bool
     */
    function del_dir(string $path): bool
    {
        if (is_dir($path)) {
            $handle = opendir($path);
            if ($handle) {
                // 遍历删除
                $item = readdir($handle);
                while (false !== $item) {
                    if ($item != "." && $item != "..") {
                        $fir = "$path/$item";
                        is_dir($fir) ? del_dir($fir) : unlink($fir);
                    }
                    $item = readdir($handle);
                }
                closedir($handle);
                return rmdir($path);
            }
            clearstatcache();
        }
        return false;
    }
}
if (!function_exists('camelize')) {
    function camelize($str, $separator = '_'): string
    {
        $str = $separator . str_replace($separator, " ", strtolower($str));
        return ltrim(str_replace(" ", "", ucwords($str)), $separator);
    }
}

if (!function_exists('safe_file')) {
    function safe_file(string $dir, string $file, string $suffix = ''): string
    {
        $ds   = DIRECTORY_SEPARATOR;
        $file = str_replace([$ds, '.'], ['', ''], $file);
        
        return "$dir$ds$file$suffix";
    }
}

/**
 * 判断安装锁
 *
 * @return bool
 */
function is_locked(): bool
{
    return file_exists(LOCK);
}

/** @noinspection PhpUnused */
function null2empty(array $a): array
{
    return array_map(fn ($v) => $v ?? '', $a);
}

